/*
 * Matt Adams
 * TCSS 558 HW1
 * Fall 2013
 */
package client;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.util.Arrays;

import tools.Log;

/**
 * Handles UDP client functionality for spelling client-server program.
 * 
 * @author Matt Adams
 */
public class UDPSpellingClient extends SpellingClient {
    //Static variables used in udp client 
    private static final int SOCKET_TIMEOUT = 10000;
    private static final Charset CHARSET = Charset.forName("US-ASCII");
    
    /**
     * Socket used to transfer data.
     */
    private DatagramSocket socket;

    /**
     * Constructs UDPSpellingClient object.
     * 
     * @param hostPortWordArray Listing of host, port, and words to be queried.
     */
    public UDPSpellingClient(final String[] hostPortWordArray) {
        super(hostPortWordArray);
        setupSocket();
    }
    
    /**
     * Starts client.
     */
    public void start() {
        
        for (int i = 0; i < this.queryWords.size(); i++) {
            Log.out("Querying server for word: " + this.queryWords.get(i));
            queryServer(this.queryWords.get(i));
        }
        this.socket.close();
    }
    
    /**
     * Receives data from server.
     */
    protected void receive(final String word) {
        final byte[] wordBytes = new byte[54000];
        DatagramPacket packet = new DatagramPacket(wordBytes, wordBytes.length);
        try {
            this.socket.receive(packet);
        } catch (IOException e) {
            Log.err("IOException receiving datagram packet: " + e);
        }
        processQueryResponse(word, packet.getData(), packet.getLength());
    }
    
    /**
     * Sends data to server.
     */
    protected void send(final String word) {
        final byte[] sendPacketBytes = new byte[256];
        final byte[] queryWordBytes = word.getBytes(CHARSET);
        for (int i = 0; i < queryWordBytes.length; i++) {
            sendPacketBytes[i] = queryWordBytes[i];
        }
        DatagramPacket packet = new DatagramPacket(sendPacketBytes, queryWordBytes.length + 1, this.address, this.port);
        try {
            this.socket.send(packet);
        } catch (IOException e) {
            Log.err("IOException sending datagram packet: " + e.getMessage());
        }
    }
    
    /**
     * Checks that the query response from the server is correct.
     * 
     * @param word Word queried.
     * @param responseData Response data from server.
     * @param dataLength Length of data in response.
     */
    private void processQueryResponse(final String word, final byte[] responseData, final int dataLength) {
        
        final boolean wordFound = checkResponseData(responseData, dataLength);
        String queriedWord = null;
        String suggestedWords = null;
        int numberOfWords = -1;
        int i = 0;
        while (responseData[i] != 0) {
            i += 1;
        }
        final byte[] queriedWordBytes = Arrays.copyOfRange(responseData, 0, i);
        queriedWord = new String(queriedWordBytes, CHARSET);
        if (!queriedWord.equals(word)) {
            Log.err("Server response contains incorrect queried word. Queried word: " + word + ". Returned word: " + queriedWord);
            errorClose(this.socket);
        }
        if (!wordFound) {          
            int j = i + 1;
            int count = 0;
            //counts number of suggested words in response data
            while (responseData[j] != 0) {
                j += 1;
                if (responseData[j] == 0) {
                    count++;
                    j += 1;
                }
            }
            if (count > 0) {
                final byte[] suggestedWordBytes = Arrays.copyOfRange(responseData, i + 2, j);
                suggestedWords = new String(suggestedWordBytes, CHARSET);
            }
            try {              
                Byte b = new Byte(responseData[i + 1]);
                numberOfWords = b.intValue();
            } catch (NumberFormatException e) {
                Log.err("Not valid integer for number of close words in packet: " + e);
                errorClose(this.socket);
            }
            if (count != numberOfWords) {
                Log.err("Number of close words does not match number in packet.");
                errorClose(this.socket);
            }
        }   
        displayQueryResponse(queriedWord, numberOfWords, suggestedWords);
    }
    
    /**
     * Checks the response data from the server for correctness.
     * 
     * @param packetData Response data.
     * @param dataLength Length of data.
     * @return Returns whether or not the queried word was spelled correctly.
     */
    private boolean checkResponseData(final byte[] packetData, final int dataLength) {
        if (packetData[0] == 0) {
            Log.err("First character in return packet is null.");
            errorClose(this.socket);
        }
        if (packetData[packetData.length - 1] != 0) {
            Log.err("Last character of return packet is not null.");
            errorClose(this.socket);
        }
        int index = 0;
        while (index < packetData.length && packetData[index] != 0) {
            index++;
        }
        if (packetData.length - 1 == index) {
            Log.err("No bytes after query word in return packet.");
            errorClose(this.socket);
        }
        boolean correctSpelling = true;
        index++;
        if (packetData[index] != 0) {
            correctSpelling = false;
        } else if (dataLength - 1 == index) {
            correctSpelling = false;
        }
        return correctSpelling;
    }
    
    /**
     * Sets up socket.
     */
    private void setupSocket() {
        try {
            this.socket = new DatagramSocket();
            this.socket.setSoTimeout(SOCKET_TIMEOUT);
            Log.out("Socket opened on port: " + this.socket.getLocalPort());
        } catch (SocketException e) {
            Log.err("Failed to create datagram socket: " + e.getMessage());
            errorClose();
        }     
    }

    /**
     * Main to run client.
     * @param hostPortWordArray Listing of host, port, and words to be queried.
     */
    public static void main(String[] hostPortWordArray) {
        final UDPSpellingClient udpSpellingClient = new UDPSpellingClient(hostPortWordArray);
        udpSpellingClient.start();
    }
}
